package com.weilus.filters;

import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.OAuth2Request;
import reactor.core.publisher.Mono;

import java.util.Set;
import java.util.stream.Stream;

public class ReactiveRedisAuthentication {
    public static final OAuth2Authentication NULL_AUTHENTICATION = new OAuth2Authentication(new OAuth2Request(),null);
    private ReactiveRedisTemplate<String, OAuth2Authentication> redisTemplate;

    public ReactiveRedisAuthentication(ReactiveRedisConnectionFactory factory){
        RedisSerializer jdkSerializer = new JdkSerializationRedisSerializer();
        RedisSerializationContext.RedisSerializationContextBuilder<String, ?> builder = RedisSerializationContext.newSerializationContext(new StringRedisSerializer());
        RedisSerializationContext context = builder.value(jdkSerializer).build();
        this.redisTemplate = new ReactiveRedisTemplate<>(factory, context);
    }

    public Mono<Auth> loadAuth(String token){
        return redisTemplate.opsForValue().get("auth:"+token).defaultIfEmpty(NULL_AUTHENTICATION).map(Auth::new);
    }


    class Auth {
        public OAuth2Authentication authentication;
        private StandardEvaluationContext context = new StandardEvaluationContext();
        private SpelExpressionParser parser = new SpelExpressionParser();

        public Auth(OAuth2Authentication authentication){
            this.authentication = authentication;
            this.context.setRootObject(this);
        }

        public boolean authenticated(){
            return authentication.isAuthenticated();
        }

        public boolean hasRoles(String... role){
            return Stream.of(role).anyMatch(authentication.getAuthorities()::contains);
        }

        public boolean hasScopes(String... scope){
            return Stream.of(scope).anyMatch(authentication.getOAuth2Request().getScope()::contains);
        }

        public boolean sPel(Set<AuthProperties.AuthMatcher> matches){
            return matches.stream().allMatch(match->parser.parseExpression(match.getExpression(), new TemplateParserContext()).getValue(context, Boolean.class));
        }

    }
}
